﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tycho.Runtime;

namespace Tycho.Transactions {
    public class TransactionalObject : AnyObject, ITransactionalObject {
        AnyObject LiveObject;
        public long Version { get; private set; }
        HashSet<AnyObject> ReadOnlyMethods;
        bool _isTransactional;

        public TransactionalObject (AnyObject liveObject) : this (liveObject, null) { }

        public TransactionalObject (AnyObject liveObject, IEnumerable<AnyObject> readOnlyMethods) {
            LiveObject = liveObject;

            if (readOnlyMethods != null) {
                ReadOnlyMethods = new HashSet<AnyObject> (readOnlyMethods);
            } else {
                ReadOnlyMethods = null;
            }
        }

        bool IsMethodReadOnly (AnyObject name) {
            if (ReadOnlyMethods != null) {
                return ReadOnlyMethods.Contains (name);
            } else {
                return false;
            }
        }

        public override AnyObject this [AnyObject index] {
            get {
                return GetObject (false) [index];
            }
            set {
                GetObject (true) [index] = value;
            }
        }

        public override void AddMethod (AnyObject name, AnyObject method) {
            GetObject (true).AddMethod (name, method);
        }

        public override void AddPropertyGetter (AnyObject name, AnyObject getter, bool overrideMember) {
            GetObject (true).AddPropertyGetter (name, getter, overrideMember);
        }

        public override void AddPropertySetter (AnyObject name, AnyObject setter, bool overrideMember) {
            GetObject (true).AddPropertySetter (name, setter, overrideMember);
        }

        public override AnyObject InvokeMethod (AnyObject self, AnyObject name, params AnyObject [] arguments) {
            return GetObject (!IsMethodReadOnly (name)).InvokeMethod (self, name, arguments);
        }

        public AnyObject Object {
            get {
                return GetObject (false);
            }
        }

        AnyObject GetObject (bool toModify) {
            if (_isTransactional) {
                return (AnyObject) TransactionalObjectManager.Manager.TransactionLog.GetObject (this, toModify);
            } else {
                return LiveObject;
            }
        }

        public override bool IsNull {
            get {
                return GetObject (false).IsNull;
            }
        }

        public override bool TryCastTo<T> (out T result) {
            return GetObject (!typeof (T).IsValueType).TryCastTo<T> (out result);
        }

        public override IEnumerable<AnyObject> ObjectReferences {
            get {
                return TransactionLocalObject.ObjectReferences;
            }
        }

        public bool IsTransactional {
            get {
                return _isTransactional;
            }
        }

        public void Transactionalise () {
            _isTransactional = true;
        }

        private AnyObject TransactionLocalObject {
            get {
                return (AnyObject) TransactionalObjectManager.Manager.TransactionLog.GetObject (this, false);
            }
        }

        public void Commit () {
            TransactionLog.LocalObject localObject = TransactionalObjectManager.Manager.TransactionLog.GetLocalObject (this, false);
            LiveObject = (AnyObject) localObject.Object;
            if (localObject.Modified) {
                Version++;
            }
        }

        public void RollBack () {
            // do nothing
        }

        public bool Prepare () {
            TransactionLog.LocalObject localObject = TransactionalObjectManager.Manager.TransactionLog.GetLocalObject (this, false);

            return localObject.Version == Version;
        }

        public TransactionLog.LocalObject CreateTransactionLocalObject () {
            return new TransactionLog.LocalObject (LiveObject.ShallowCopy (), Version);
        }

        public TransactionLog.LocalObject CreateNestedTransactionLocalObject (TransactionLog.LocalObject localObject) {
            return new TransactionLog.LocalObject (((AnyObject) localObject.Object).ShallowCopy (), localObject.Version);
        }

        public override string ToString (HashSet<AnyObject> done) {
            return "(t) " + GetObject (false).ToString (done);
        }

        public override bool Equals (object obj) {
            return GetObject (false).Equals (obj);
        }

        public override int GetHashCode () {
            return GetObject (false).GetHashCode ();
        }
    }
}
